/**
 * 
 */
package lt.vin.currency.server;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import lt.vin.currency.shared.Rate;
import lt.vin.currency.shared.Source;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullWriter;
import org.w3c.dom.Document;
import org.w3c.tidy.Tidy;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;



/**
 * @author Vincentas
 * 
 */
public class SourceReader {
	
	private final Source source;
	
	private String data = null;
	
	private Document document;
	
	/*
	 * Workaround : http://code.google.com/p/googleappengine/issues/detail?id=1452
	 */			
	private final TransformerFactory transformerFactory = javax.xml.transform.TransformerFactory.newInstance("org.apache.xalan.processor.TransformerFactoryImpl", Thread.currentThread().getContextClassLoader());
	
	private final DefaultErrorHandler errorHandler = new DefaultErrorHandler();
	
	{
		transformerFactory.setErrorListener(errorHandler);
	}
	
	private final CurrencyHandler handler = new CurrencyHandler();

	private final static Tidy tidy = new Tidy();
	
	static {
		tidy.setXHTML(true);
		tidy.setErrout(new PrintWriter(new NullWriter()));
	}
	
	public SourceReader(Source source) {
		this.source = source;
	}
	
	public String getData() throws TransformerConfigurationException, MalformedURLException, TransformerException, IOException {
		if (data == null) {
			StringWriter writer = new StringWriter();
	
			Transformer transformer = transformerFactory.newTransformer(new StreamSource(IOUtils.toInputStream(source.getTransformation())));
			transformer.transform(new DOMSource(getDocument()), new StreamResult(writer));
	
			data = writer.toString();
		}
		
		return data;
	}
	
	private Document getDocument() throws MalformedURLException, IOException {
		if (document == null) {
			document = tidy.parseDOM(new URL(source.getUrl()).openStream(), null);
		}
		
		return document;
	}
	
	
	public String getRawData() throws MalformedURLException, IOException, TransformerException {
		Document document = getDocument();
		
		tidy.pprint(document, System.out);
		
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
		
		StringWriter writer = new StringWriter();
		transformer.transform(new DOMSource(document), new StreamResult(writer));		
		
		return writer.toString();
	}
	
	public List<Rate> getRates() throws SAXException, ParserConfigurationException, IOException, TransformerConfigurationException, TransformerException {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		factory.setValidating(true);
		factory.setNamespaceAware(true);
		factory.setFeature("http://apache.org/xml/features/validation/schema", true);
		factory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);	
		
		SAXParser parser = factory.newSAXParser();
		
		XMLReader reader = parser.getXMLReader();
		reader.setErrorHandler(errorHandler);

		reader.setContentHandler(handler);
		reader.parse(new InputSource(IOUtils.toInputStream(getData())));
				
		return handler.getRates();
	}
	
	class CurrencyHandler extends DefaultHandler {

		private List<Rate> rates = new ArrayList<Rate>();
		
		private Rate rate = null;
		
		private StringBuffer text = new StringBuffer();
		
		public List<Rate> getRates() {
			return rates;
		}

		/* (non-Javadoc)
		 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
		 */
		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
			text.append(ch, start, length);
		}

		/* (non-Javadoc)
		 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
		 */
		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			if (localName.equalsIgnoreCase("rate")) {
				rate = new Rate();
			}
		}

		/* (non-Javadoc)
		 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
		 */
		@Override
		public void endElement(String uri, String localName, String qName) throws SAXException {
			if (localName.equalsIgnoreCase("currency")) {
				rate.setCurrency(getText());
			} else if (localName.equalsIgnoreCase("amount")) {
				rate.setAmount(getInteger());
			} else if (localName.equalsIgnoreCase("buy")) {
				rate.setBuy(new BigDecimal(getText()));
			} else if (localName.equalsIgnoreCase("sell")) {
				rate.setSell(new BigDecimal(getText()));
			} else if (localName.equals("bank")) {
				rate.setBankCode(getText());
			} else if (localName.equalsIgnoreCase("rate")) {
				rates.add(rate);
			} else if (localName.equalsIgnoreCase("date")) {
				rate.setDate(getDate());
			}
		}		
		
		private String getText() {
			try {
				return text.toString();
			} finally {
				text.setLength(0);
			}
		}
		
		private int getInteger() {
			return Integer.valueOf(getText());
		}
		
		private Date getDate() throws SAXException {
			try {
				return new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss").parse(getText());
			} catch (ParseException e) {
				throw new SAXException(e);
			}
		}
	}
	
	private class DefaultErrorHandler implements ErrorHandler, ErrorListener {

		@Override
		public void warning(TransformerException exception) throws TransformerException {throw exception;}

		@Override
		public void error(TransformerException exception) throws TransformerException {throw exception;}

		@Override
		public void fatalError(TransformerException exception) throws TransformerException {throw exception;}
		
		@Override
		public void warning(SAXParseException exception) throws SAXException {throw exception;}
		
		@Override
		public void error(SAXParseException exception) throws SAXException {throw exception;}

		@Override
		public void fatalError(SAXParseException exception) throws SAXException {throw exception;}		
	}
	
	public String getDataXml() throws TransformerConfigurationException, MalformedURLException, TransformerException, IOException {
		String result = getData();
		
		try {
			Transformer transformer = transformerFactory.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
			
			StringWriter writer = new StringWriter();
			transformer.transform(new StreamSource(IOUtils.toInputStream(result)), new StreamResult(writer));			
			result = writer.toString();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}
		
		return result;
	}
}
